package com.easylinkin.sm.util;


import com.easylinkin.sm.entity.Department;
import com.easylinkin.sm.permission.impl.SimpleJpaCustomerPermission;
import com.easylinkin.sm.permission.impl.SimpleJpaDepartmentPermission;
import com.easylinkin.sm.permission.jpa.JpaAreaPermission;
import com.easylinkin.sm.permission.jpa.JpaCustomerPermission;
import com.easylinkin.sm.permission.jpa.JpaDepartmentPermission;

import java.util.Objects;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import lombok.experimental.UtilityClass;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;
import site.morn.bean.BeanCaches;
import site.morn.boot.data.entity.OrganizedEntity.Fields;
import site.morn.boot.jpa.JpaConditionUtils;
import site.morn.boot.jpa.JpaPredicate;
import site.morn.boot.jpa.SpecificationBuilder;
import site.morn.data.Displayable;
import site.morn.framework.context.AccountContext;
import site.morn.framework.context.DepartmentContextProducer;
import site.morn.framework.context.dto.BaseLoginInfo;
import site.morn.framework.entity.BaseDepartment;
import site.morn.framework.entity.BaseUser;

/**
 * 数据权限工具类
 *
 * @author TianGanLin
 * @since 0.1.1, 2019/5/16
 */
@UtilityClass
public class DataPermissionUtils {

  private static final String DISPLAYABLE_PROPERTY = "display";
  private static final String LOCKABLE_PROPERTY = "locked";

  /**
   * 查询可显示的数据
   *
   * @param <T> 实体类型
   * @return 查询条件
   */
  public <T extends Displayable> Specification<T> display() {
    return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder
        .equal(root.get(DISPLAYABLE_PROPERTY), true);
  }

  /**
   * 查询未锁定的数据
   *
   * @param <T> 实体类型
   * @return 查询条件
   */
  public <T extends Displayable> Specification<T> unlock() {
    return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder
        .equal(root.get(LOCKABLE_PROPERTY), false);
  }

  /**
   * 区域权限
   */
  public JpaAreaPermission areaPermission() {
    return BeanCaches.getBean(JpaAreaPermission.class);
  }

  /**
   * 客户权限
   */
  public JpaCustomerPermission customerPermission() {
    return new SimpleJpaCustomerPermission();
  }

  /**
   * 组织机构权限
   */
  public JpaDepartmentPermission departmentPermission() {
    return new SimpleJpaDepartmentPermission();
  }

  /**
   * 过滤当前机构数据
   *
   * @param <T> 实体类型
   * @return 查询条件
   * @deprecated {@link #departmentPermission()}
   */
  @Deprecated
  public <T> Specification<T> currentOrganization() {
    BaseUser baseUser = AccountContext.currentUser();
    if (Objects.isNull(baseUser)) {
      return null;
    }
    Long departmentId = baseUser.getDepartmentId();
    if (Objects.isNull(departmentId)) {
      return null;
    }
    return currentOrganization(departmentId);
  }

  /**
   * 过滤指定机构
   *
   * @param departmentId 组织机构编号
   * @param <T> 实体类型
   * @return 查询条件
   * @deprecated {@link #departmentPermission()}
   */
  @Deprecated
  public <T> Specification<T> currentOrganization(Long departmentId) {
    return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder
        .equal(root.get(Fields.departmentId), departmentId);
  }

  /**
   * 过滤当前机构及子机构数据
   *
   * @param <T> 实体类型
   * @return 查询条件
   * @deprecated {@link #departmentPermission()}
   */
  @Deprecated
  public <T> Specification<T> subOrganizations() {
    return subOrganizations(Fields.departmentId);
  }

  /**
   * 过滤指定机构及子机构数据
   *
   * @param departmentId 机构编号
   * @param <T> 实体类型
   * @return 查询条件
   * @deprecated {@link #departmentPermission()}
   */
  @Deprecated
  public <T> Specification<T> subOrganizations(Long departmentId) {
    if (Objects.isNull(departmentId)) {
      return subOrganizations();
    }
    DepartmentContextProducer departmentProducer = BeanCaches.bean(DepartmentContextProducer.class);
    BaseDepartment department = departmentProducer.get(departmentId);
    String searchCode = department.getSearchCode();
    return subOrganizations(Fields.departmentId, searchCode);
  }

  /**
   * 过滤当前机构及子机构数据
   *
   * @param <T> 实体类型
   * @return 查询条件
   * @deprecated {@link #departmentPermission()}
   */
  @Deprecated
  public <T> Specification<T> subOrganizations(String departmentIdName) {
    BaseLoginInfo loginInfo = AccountContext.loginInfo();
    BaseDepartment department = loginInfo.getDepartment();
    String searchCode = department.getSearchCode();
    return subOrganizations(departmentIdName, searchCode);
  }

  /**
   * 过滤指定机构
   *
   * @param departmentIdName 组织机构编号属性名
   * @param searchCode 组织机构查询码
   * @param <T> 实体类型
   * @return 查询条件
   * @deprecated {@link #departmentPermission()}
   */
  @Deprecated
  public <T> Specification<T> subOrganizations(String departmentIdName, String searchCode) {
    return SpecificationBuilder.specification((root, query, builder, predicate) -> {
      Path<Long> departmentId = root.get(departmentIdName); // 所属组织机构编号
      appendSubOrganizationsPredicate(query, builder, predicate, departmentId, searchCode);
    });
  }

  /**
   * 子组织机构查询条件
   *
   * @param query 标准查询
   * @param builder 查询构建
   * @param departmentId 组织机构编号（实体属性）
   * @param searchCodeString 组织机构查询码
   * @return 查询条件
   */
  private Predicate subOrganizationsPredicate(CriteriaQuery<?> query, CriteriaBuilder builder,
      Path<Long> departmentId, String searchCodeString) {

    String likeString = JpaConditionUtils.startWith(searchCodeString);

    if (StringUtils.isEmpty(likeString)) {
      return null;
    }

    // 从组织机构创建子查询
    // 子查询：select id from Department where searchCode like ':searchCode%'
    Subquery<Long> subQuery = query.subquery(Long.class);
    Root<Department> from = subQuery.from(Department.class);

    Path<Long> id = from.get(BaseDepartment.Fields.id); // 组织机构编号
    Path<String> searchCode = from.get(BaseDepartment.Fields.searchCode); // 组织机构查询码
    Path<Boolean> display = from.get(Department.Fields.display); // 组织机构软删除

    Predicate startWith = JpaConditionUtils.predicate(searchCode, likeString, builder::like);
    Predicate isDisplay = builder.isTrue(display);

    subQuery.select(id).where(startWith, isDisplay);

    // where departmentId in (子查询)
    return departmentId.in(subQuery);
  }

  /**
   * 子组织机构查询条件
   *
   * @param query 标准查询
   * @param builder 查询构建
   * @param predicate 查询断言
   * @param departmentId 组织机构编号（实体属性）
   * @param searchCodeString 组织机构查询码
   */
  public void appendSubOrganizationsPredicate(CriteriaQuery<?> query, CriteriaBuilder builder,
      JpaPredicate predicate, Path<Long> departmentId, String searchCodeString) {
    Predicate in = subOrganizationsPredicate(query, builder, departmentId, searchCodeString);
    predicate.appendAnd(in);
  }
}
